﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;

namespace TransformInteraction
{
    [TemplatePart(Name = "PART_ClickControls", Type = typeof(Panel))]
    [TemplatePart(Name = "PART_RotateControls", Type = typeof(Panel))]
    [TemplatePart(Name = "PART_TranslateControls", Type = typeof(Panel))]
    [TemplatePart(Name = "PART_ScaleControls", Type = typeof(Panel))]
    [TemplatePart(Name = "PART_Rotation", Type = typeof(RotateTransform))]
    [TemplatePart(Name = "PART_Scale", Type = typeof(ScaleTransform))]
    [TemplatePart(Name = "PART_Translation", Type = typeof(TranslateTransform))]
    [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
    [TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
    public class TransformInteractionControl : ContentControl, INotifyPropertyChanged
    {
        #region Fields

        private RotateTransform _rotation = null;
        private TranslateTransform _translation = null;
        private ScaleTransform _scale = null;
        private InteractionMode _iteractionMode = InteractionMode.None;
        private Point _previousPosition = new Point();
        private static int zIndex = 0;

        #endregion Fields
        #region Constructors

        public TransformInteractionControl()
        {
            this.DefaultStyleKey = typeof(TransformInteractionControl);

            this.MouseEnter += new MouseEventHandler(OnControlMouseEnter);
            this.MouseLeave += new MouseEventHandler(OnControlMouseLeave);
            this.MouseMove += new MouseEventHandler(OnControlMouseMove);
            this.MouseWheel += new MouseWheelEventHandler (OnControlMouseClick);  
            this.MouseLeftButtonUp += new MouseButtonEventHandler(OnControlMouseLeftButtonUp);
        }

        #endregion Contructors
        #region Events
        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #endregion INotifyPropertyChanged

        public event EventHandler<EventArgs> Click;
        private void OnClick()
        {
            if (Click != null)
            {
                Click(this, EventArgs.Empty);
            }
        }

        #endregion Events
        #region Properties

        public static readonly DependencyProperty ScaleProperty = DependencyProperty.Register("Scale", typeof(double), typeof(TransformInteractionControl), new PropertyMetadata(new PropertyChangedCallback(OnScalePropertyChanged)));
        public double Scale
        {
            get { return (double)GetValue(ScaleProperty); }
            set { SetValue(ScaleProperty, value); }
        }

        private static void OnScalePropertyChanged(object sender, DependencyPropertyChangedEventArgs d)
        {
            TransformInteractionControl transformControl = sender as TransformInteractionControl;
            if (transformControl != null)
            {
                double newScale = (double)d.NewValue;
                if (newScale < 0.1)
                {
                    newScale = 0.1;
                }

                if (transformControl._scale != null && newScale != transformControl._scale.ScaleX)
                {
                    transformControl._scale.ScaleX = newScale;
                    transformControl._scale.ScaleY = newScale;
                    transformControl.NotifyPropertyChanged("Scale");
                }
            }
        }

        public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(TransformInteractionControl), new PropertyMetadata(new PropertyChangedCallback(OnAnglePropertyChanged)));
        public double Angle
        {
            get { return (double)GetValue(AngleProperty); }
            set { SetValue(AngleProperty, value); }
        }

        private static void OnAnglePropertyChanged(object sender, DependencyPropertyChangedEventArgs d)
        {
            TransformInteractionControl transformControl = sender as TransformInteractionControl;
            if (transformControl != null)
            {
                double newAngle = (double)d.NewValue;
                if (transformControl._rotation != null && newAngle != transformControl._rotation.Angle)
                {
                    transformControl._rotation.Angle = newAngle;
                    transformControl.NotifyPropertyChanged("Angle");
                }
            }
        }


        public static readonly DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double), typeof(TransformInteractionControl), new PropertyMetadata(new PropertyChangedCallback(OnXPropertyChanged)));
        public double X
        {
            get { return (double)GetValue(XProperty); }
            set { SetValue(XProperty, value); }
        }

        private static void OnXPropertyChanged(object sender, DependencyPropertyChangedEventArgs d)
        {
            TransformInteractionControl transformControl = sender as TransformInteractionControl;
            if (transformControl != null)
            {
                double newX = (double)d.NewValue;
                if (transformControl._translation != null && newX != transformControl._translation.X)
                {
                    transformControl._translation.X = newX;
                    transformControl.NotifyPropertyChanged("X");
                }
            }
        }


        public static readonly DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double), typeof(TransformInteractionControl), new PropertyMetadata(new PropertyChangedCallback(OnYPropertyChanged)));
        public double Y
        {
            get { return (double)GetValue(YProperty); }
            set { SetValue(YProperty, value); }
        }

        private static void OnYPropertyChanged(object sender, DependencyPropertyChangedEventArgs d)
        {
            TransformInteractionControl transformControl = sender as TransformInteractionControl;
            if (transformControl != null)
            {
                double newY = (double)d.NewValue;
                if (transformControl._translation != null && newY != transformControl._translation.Y)
                {
                    transformControl._translation.Y = newY;
                    transformControl.NotifyPropertyChanged("Y");
                }
            }
        }

        #endregion Properties
        #region Methods

        private double GetScale(Point currentPosition)
        {
            double halfWidth = ActualWidth / 2.0;
            double halfHeight = ActualHeight / 2.0;
            double x = (currentPosition.X - (X + halfWidth)) / halfWidth;
            double y = (currentPosition.Y - (Y + halfHeight)) / halfHeight;

            double newScale = Math.Sqrt(Math.Pow(x, 2.0) + Math.Pow(y, 2.0));
            return newScale;
        }

        private double GetDeltaAngle(Point currentPosition)
        {            
            double localCenterX = this.ActualWidth / 2.0;
            double localCenterY = this.ActualHeight / 2.0;            
            Point center = new Point(_translation.X + localCenterX, _translation.Y + localCenterY);            
            Point previousPosition = new Point(_previousPosition.X - center.X, _previousPosition.Y - center.Y);
            currentPosition.X -= center.X;
            currentPosition.Y -= center.Y;

            double previousAngle = Math.Atan2(previousPosition.Y, previousPosition.X);
            double currentAngle = Math.Atan2(currentPosition.Y, currentPosition.X);

            // Return the delta angle and convert from radians to degrees (180/pi = 57.2957...).
            return (currentAngle - previousAngle) * 57.2957;
        }

        private void Rotate(double deltaAngle)
        {
            Angle += deltaAngle;
        }

        private void Translate(double deltaX, double deltaY)
        {
            X += deltaX;
            Y += deltaY;
        }

        private void ScaleXY(double newScale)
        {
            Scale = newScale;
        }

        public override void OnApplyTemplate()
        {
            Panel clickControls = this.GetTemplateChild("PART_ClickControls") as Panel;
            if (clickControls == null)
            {
                throw new NullReferenceException("Could not find templated PART: PART_ClickControls");
            }
            clickControls.MouseLeftButtonDown += new MouseButtonEventHandler(OnClickControlsMouseLeftButtonDown);

            Panel rotateControls = this.GetTemplateChild("PART_RotateControls") as Panel;
            if (rotateControls == null)
            {
                throw new NullReferenceException("Could not find templated PART: PART_RotateControls");
            }
            rotateControls.MouseLeftButtonDown += new MouseButtonEventHandler(OnRotateControlsMouseLeftButtonDown);

            Panel translateControls = this.GetTemplateChild("PART_TranslateControls") as Panel;
            if (translateControls == null)
            {
                throw new NullReferenceException("Could not find templated PART: PART_TranslateControls");
            }
            translateControls.MouseLeftButtonDown += new MouseButtonEventHandler(OnTranslateControlsMouseLeftButtonDown);

            Panel scaleControls = this.GetTemplateChild("PART_ScaleControls") as Panel;
            if (scaleControls == null)
            {
                throw new NullReferenceException("Could not find templated PART: PART_ScaleControls");
            }
            scaleControls.MouseLeftButtonDown += new MouseButtonEventHandler(OnScaleControlsMouseLeftButtonDown);

            _translation = this.GetTemplateChild("PART_Translation") as TranslateTransform;
            if (_translation == null)
            {
                throw new NullReferenceException("Could not find templated PART: PART_Translation");
            }
            _translation.X = X;
            _translation.Y = Y;

            _rotation = this.GetTemplateChild("PART_Rotation") as RotateTransform;
            if (_rotation == null)
            {
                throw new NullReferenceException("Could not find templated PART: PART_Rotation");
            }
            _rotation.Angle = Angle;

            _scale = this.GetTemplateChild("PART_Scale") as ScaleTransform;
            if (_scale == null)
            {
                throw new NullReferenceException("Could not find templated PART: PART_Scale");
            }

            if (Scale == 0)
            {
                Scale = 1.0;
            }

            _scale.ScaleX = Scale;
            _scale.ScaleY = Scale;

            this.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);

            base.OnApplyTemplate();
        }
        
        private void OnControlMouseMove(object sender, MouseEventArgs e)
        {
            if (_iteractionMode != InteractionMode.None)
            {
                Point currentPosition = e.GetPosition(this);

                if (_iteractionMode == InteractionMode.Translating)
                {
                    Translate(currentPosition.X - _previousPosition.X, currentPosition.Y - _previousPosition.Y);
                }
                else if (_iteractionMode == InteractionMode.Rotating)
                {
                    double deltaAngle = GetDeltaAngle(currentPosition);
                    Rotate(deltaAngle);
                }
                else if (_iteractionMode == InteractionMode.Scaling)
                {
                    double newScale = GetScale(currentPosition);
                    ScaleXY(newScale);
                }

                _previousPosition = currentPosition;
            }
        }

        private void OnTranslateControlsMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _previousPosition = e.GetPosition(this);
            this.CaptureMouse();
            _iteractionMode = InteractionMode.Translating;
        }

        private void OnRotateControlsMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _previousPosition = e.GetPosition(this);
            this.CaptureMouse();
            _iteractionMode = InteractionMode.Rotating;
        }

        private void OnScaleControlsMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _previousPosition = e.GetPosition(this);
            this.CaptureMouse();
            _iteractionMode = InteractionMode.Scaling;
        }

        private void OnClickControlsMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            OnClick();            
        }

        private void OnControlMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _iteractionMode = InteractionMode.None;
        }

        private void OnControlMouseLeave(object sender, MouseEventArgs e)
        {
            VisualStateManager.GoToState(this, "Normal", true);
        }

        private void OnControlMouseEnter(object sender, MouseEventArgs e)
        {
            VisualStateManager.GoToState(this, "MouseOver", true);
        }
        private void OnControlMouseClick(object sender, MouseEventArgs e)
        {
            VisualStateManager.GoToState(this, "Pressed", true);
        }


        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Canvas.SetZIndex(this, zIndex);
            IncreaseZIndex();
        }

        private void IncreaseZIndex()
        {
            if (zIndex < int.MaxValue)
            {
                zIndex++;
            }
            else
            {
                zIndex = 0;
            }
        }

        #endregion Methods
    }
}
